package net.form.processing;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.form.ObjetoClasificador;
import net.form.Pixel;
import net.form.processing.classification.EvaluadorClase;
import net.form.processing.classification.EvaluadorHistograma;
import net.form.processing.classification.EvaluadorRasgo;
import net.model.Clase;
import net.model.ClaseObjeto;
import net.model.Grado;
import net.model.Background;
import net.model.Histogram;
import net.model.RasgoClase;
import net.model.RasgoObjeto;
import net.model.RubroCalidad;
import net.model.RectangleReference;
import net.service.ClaseManager;
import net.service.GradoManager;
import net.service.BackgroundManager;
import net.service.ObjetoManager;
import net.service.RasgoClaseManager;
import net.service.RasgoManager;
import net.service.RubroCalidadManager;
import net.service.RectangleReferenceManager;


public class Clasificador {
	
	/**
	 * Por el ahora es uno solo MAIZ
	 */
	public static final String NOMBRE_SISTEMA = "MAIZ";

	

	public static String CLASE_INDETERMINADO = "INDETERMINADO";
	
	public static String RUBRO_CALIDAD_HUMEDAD = "HUMEDAD";
	
	public static String RUBRO_CALIDAD_PESO_HECTOLITRICO = "PESO_HECTOLITRICO";

	private Map<EvaluadorClase, List<ObjetoClasificador>> clasificacion = new HashMap<EvaluadorClase, List<ObjetoClasificador>>();
	
	private List<ObjetoClasificador> clasificacionInicial = new  ArrayList<ObjetoClasificador>();
	
	//private ObjetoReferencia objetoReferencia= null; 
	
	private RectangleReference configuracion;
	
	private Background background;
	
	private List<Clase> clases = null;

	private Integer cantidadObjetos = null;
	
	private List<ObjetoClasificador> clasificadosIncorrectamente = new ArrayList<ObjetoClasificador>();
	
	private List<RubroCalidad> rubrosCalidad = new ArrayList<RubroCalidad>();
	
	private List<Grado> gradosCalidad = new ArrayList<Grado>();
	
	private RectangleReferenceManager systemConfigurationManagerImpl;
	
	private  BackgroundManager backgroundManagerImpl ;

	private RubroCalidadManager rubroCalidadManager;
	
	private ClaseManager claseManager;

	private GradoManager gradoManager;
	
	private ObjetoManager objetoManager;
	
	private RasgoClaseManager rasgoClaseManager;

	private RasgoManager rasgoManager;

	public RectangleReferenceManager getSystemConfigurationManagerImpl() {
		return systemConfigurationManagerImpl;
	}

	public void setSystemConfigurationManagerImpl(
			RectangleReferenceManager systemConfigurationManagerImpl) {
		this.systemConfigurationManagerImpl = systemConfigurationManagerImpl;
	}

	public RubroCalidadManager getRubroCalidadManager() {
		return rubroCalidadManager;
	}

	public void setRubroCalidadManager(RubroCalidadManager rubroCalidadManager) {
		this.rubroCalidadManager = rubroCalidadManager;
	}

	public GradoManager getGradoManager() {
		return gradoManager;
	}

	public void setGradoManager(GradoManager gradoManager) {
		this.gradoManager = gradoManager;
	}

	public ObjetoManager getObjetoManager() {
		return objetoManager;
	}

	public void setObjetoManager(ObjetoManager objetoManager) {
		this.objetoManager = objetoManager;
	}

	public RasgoClaseManager getRasgoClaseManager() {
		return rasgoClaseManager;
	}

	public void setRasgoClaseManager(RasgoClaseManager rasgoClaseManager) {
		this.rasgoClaseManager = rasgoClaseManager;
	}
	
	public Clasificador(RectangleReferenceManager rectangleReferenceManager,
			BackgroundManager backgroundManager,
			RubroCalidadManager rubroCalidadManager, ClaseManager claseManager,RasgoManager rasgoManager,
			GradoManager gradoManager, ObjetoManager objetoManager,
			RasgoClaseManager rasgoClaseManager) {
		super();
		this.rubroCalidadManager = rubroCalidadManager;
		this.systemConfigurationManagerImpl = rectangleReferenceManager;
		this.backgroundManagerImpl = backgroundManager;
		this.claseManager = claseManager;
		this.gradoManager = gradoManager;
		this.objetoManager = objetoManager;
		this.rasgoClaseManager = rasgoClaseManager;
		this.rasgoManager = rasgoManager;
		configuracion = systemConfigurationManagerImpl
				.getRectangleReference(NOMBRE_SISTEMA);
		background = backgroundManagerImpl
				.findConfiguracion(NOMBRE_SISTEMA);
	}

	public Map<EvaluadorClase, List<ObjetoClasificador>> getClasificacion() {
		return clasificacion;
	}

	public void setClasificacion(Map<EvaluadorClase, List<ObjetoClasificador>> clasificacion) {
		this.clasificacion = clasificacion;
	}

	/**
	 * Obtiene la clasificacion inicial
	 * @return
	 */
	public List<ObjetoClasificador> getClasificacionInicial() {
		return clasificacionInicial;
	}
	
	/**
	 * Crea una copia de la clasificacion para guardarla como clasificacion inicial
	 * @param clasificacionInicial
	 */
	public void setClasificacionInicial(List<ObjetoClasificador> clasificacion) {
		this.clasificacionInicial = new ArrayList<ObjetoClasificador>();
		this.clasificadosIncorrectamente = new ArrayList<ObjetoClasificador>();
		for(ObjetoClasificador objeto: clasificacion){
			clasificacionInicial.add(objeto.clonar());
		}
	}

	public List<ObjetoClasificador> getClasificadosIncorrectamente() {
		return clasificadosIncorrectamente;
	}

	public void setClasificadosIncorrectamente(
			List<ObjetoClasificador> clasificadosIncorrectamente) {
		this.clasificadosIncorrectamente = clasificadosIncorrectamente;
	}

	public RectangleReference getConfiguracion() {
		return configuracion;
	}


	public void setConfiguracion(RectangleReference configuracion) {
		this.configuracion = configuracion;
	}

	/**
	 * Guarda la clasificacion en la base de datos
	 * @param objetos
	 */
	public void guardarClasificacion(){
		Set<EvaluadorClase> clases = clasificacion.keySet();
		for(EvaluadorClase c: clases){
			List<ObjetoClasificador> objetosClase = clasificacion.get(c);
			for(ObjetoClasificador obj:objetosClase){
				objetoManager.saveObjeto(obj.getObjeto());
			}
			
			for(EvaluadorRasgo er: c.getRasgos()){
				actualizarRasgoClase(er.getRasgoClase(), objetosClase);
			}
			Clase clase = c.getClase();
			actualizarClase(clase,objetosClase);
		}
	}
	
	/**
	 * Actualiza los histogramas promedio de la clase con los del objeto
	 * @param clase
	 * @param objeto
	 */
	private void actualizarHistogramaPromedio(Clase clase, ObjetoClasificador objeto, int cantObjetos){
		for(Histogram h: objeto.getHistograms()){
			Histogram histoClase =  claseManager.getHistogram(clase.getNombre(),h.getTipo());
			double[] valoresH=h.getValores();
			if (histoClase != null){
				double[] valores=histoClase.getValores();
				for(int i = 0; i < valores.length;i++){
					double valActual = valores[i];
					double valNuevo = (valActual * cantObjetos + valoresH[i]) / (cantObjetos + 1);
					valores[i] = valNuevo;
				}
				histoClase.actualizarValoresString(valores);
			}
			else{
				clase.getHistograms().add(h);
			}
		}
	}
	
	/**
	 * 
	 * @param clase
	 * @param objetosClase
	 */
	private void actualizarClase(Clase clase, List<ObjetoClasificador> objetosClase) {
		int cantObjetos = clase.getCantidadObjetos();
		for(ObjetoClasificador obj:objetosClase){
			actualizarHistogramaPromedio(clase, obj, cantObjetos);
			cantObjetos++;
		}
		cantObjetos = clase.getCantidadObjetos() + objetosClase.size();
		clase.setCantidadObjetos(cantObjetos);
		claseManager.saveClase(clase);
	}

	/**
	 * Actualiza los valores valor medio, desvio estandar, maximo y minimo del rasgo de una clase
	 * @param rasgoClase RasgoClase
	 * @param objetos Objetos pertenecientes a la clase
	 */
	private void actualizarRasgoClase(RasgoClase rasgoClase, List<ObjetoClasificador> objetos){
		Double sumValor = 0.0;
		Double sumValorCuadrado = 0.0;
		Double maximo = rasgoClase.getMaximo();
		Double minimo = rasgoClase.getMinimo();
		Double minimoReal = rasgoClase.getMinimoReal();
		Double maximoReal = rasgoClase.getMaximoReal();
		Integer cantValores = 0;
		
		if(rasgoClase.getSumValor() != null)
			sumValor = rasgoClase.getSumValor();
		if(rasgoClase.getSumValorCuadrado() != null)
			sumValorCuadrado = rasgoClase.getSumValorCuadrado();
		if(rasgoClase.getCantValores() != null)
			cantValores = rasgoClase.getCantValores();

		for(ObjetoClasificador o: objetos){
			RasgoObjeto ro = o.getRasgo(rasgoClase.getRasgo(),rasgoClase.getClase());
			Double valor = ro.getValorRasgoClase();
			if(valor != null){
				sumValor += valor;
				Double valorCuadrado = Math.pow(valor, 2); 
				sumValorCuadrado += valorCuadrado;
				if (rasgoClase.getRangoVariable() == true){
					if ((maximo != null &&  valor > maximo) || maximo == null)
						maximo = valor;
					if ((minimo != null &&  valor < minimo) || minimo == null)
						minimo = valor;
				}
				if (ro.getValor() !=  null){
					if ((minimoReal != null &&  ro.getValor() < minimoReal) || minimoReal == null)
						minimoReal = ro.getValor();
					if ((maximoReal != null &&  ro.getValor() > maximoReal) || maximoReal == null)
						maximoReal = ro.getValor();
				}
			}
			cantValores++;
		}
		
		rasgoClase.setSumValor(sumValor);
		rasgoClase.setSumValorCuadrado(sumValorCuadrado);
		rasgoClase.setCantValores(cantValores);
		rasgoClase.setMinimo(minimo);
		rasgoClase.setMaximo(maximo);
		rasgoClase.setMinimoReal(minimoReal);
		rasgoClase.setMaximoReal(maximoReal);
		
		rasgoClaseManager.saveRasgoClase(rasgoClase);		
	}
	
	/**
	 * Inicializa el hash de las clases
	 * @throws Exception 
	 */
	public void inicializarClasificacion() throws Exception{
		//objetoReferencia = new ObjetoReferencia();
//		ObjectReference.inicializarObjectReference();

		List<Clase> clases = (List<Clase>) claseManager.getAllClase(false,
				false);
		this.rubrosCalidad = (List<RubroCalidad>) rubroCalidadManager
				.getAllRubroCalidad(NOMBRE_SISTEMA);
		this.gradosCalidad = gradoManager.getAllGrado(NOMBRE_SISTEMA);

		clasificacion = new HashMap<EvaluadorClase, List<ObjetoClasificador>>();
		this.cantidadObjetos = objetoManager.getCantidadObjetos();
//		if (getGridConfiguration() != null && getGridConfiguration().getGrid() != null){
//			GridReferenceObject.setSizeGrid(getGridConfiguration().getGrid());
//		}
		
		for(Clase c: clases){
			EvaluadorClase ec = createEvaluadorClase(c);
			getClasificacion().put(ec, new ArrayList<ObjetoClasificador>());
		}		
	}
	
	/**
	 * Recupera el evaluador de una clase
	 * 
	 * @param nombreClase
	 * @return
	 */
	public EvaluadorClase getEvaluadorClase(String nombreClase) {
		Clase c = claseManager.getClase(nombreClase);
		EvaluadorClase ec = createEvaluadorClase(c);
		return ec;
	}
	
	public List<RasgoClase> getRasgoClases(String nombreRasgo){
		List<RasgoClase> rasgoClases = new ArrayList<RasgoClase>();
		Set<EvaluadorClase> clases = getClasificacion().keySet();
		for (EvaluadorClase c : clases) {
			for (EvaluadorRasgo er : c.getRasgos()) {
				RasgoClase rasgoClase = er.getRasgoClase();
				if (rasgoClase.getRasgo().getNombre().equals(nombreRasgo))
						rasgoClases.add(rasgoClase);
			}
		}
		return rasgoClases;
	}

	/**
	 * Recupera el evaluador de una clase
	 * 
	 * @param nombreClase
	 * @return
	 */
	public EvaluadorClase getEvaluadorClaseIndeterminado() {
		Clase c = claseManager.getClaseByIndeterminado(true);
		EvaluadorClase ec = createEvaluadorClase(c);
		return ec;
	}
//
//	/**
//	 * Recupera el evaluador de una clase
//	 * @param nombreClase
//	 * @return
//	 */
//	public EvaluadorClase getEvaluadorClaseObjetoReferencia(){
//		Clase c = claseManager.getClaseByObjetoReferencia(true);
//		EvaluadorClase ec = createEvaluadorClase(c);
//		return ec;	
//	}

	/**
	 * Recupera el evaluador de una clase
	 * @param nombreClase
	 * @return
	 */
	private EvaluadorClase createEvaluadorClase(Clase c){
		System.out.println("--------------------------------------------------------");
		System.out.println("Clase: " + c.getNombre());
		List<EvaluadorRasgo> rasgos = new ArrayList<EvaluadorRasgo>();
		for(RasgoClase r: c.getRasgos()){
			try {
				if (r != null){
					if (r.getRasgo().getNombreEvaluadorRasgo() != null){
						Class evaluadorClass = Class.forName(r.getRasgo().getNombreEvaluadorRasgo());
						EvaluadorRasgo er = (EvaluadorRasgo) evaluadorClass.newInstance();
						er.setRasgoClase(r);
						String nameClase=evaluadorClass.toString();
						if (nameClase.contains("EvaluadorHistograma")) {
							EvaluadorHistograma eh = (EvaluadorHistograma) er;
							eh.setClaseManager(claseManager);
						}
						rasgos.add(er);
						System.out.println(r.getRasgo()+ " - minimo: " + er.getMinimo() + ", maximo:" + er.getMaximo() + " [minimo: " + er.getRasgoClase().getMinimo() + ", maximo: " + er.getRasgoClase().getMaximo()+ ", media: " + er.getRasgoClase().getMedia()+ ", Desvio Estandar: " + er.getRasgoClase().getDesvioEstandar() + "]");
					}
					else{
						EvaluadorRasgo er = new EvaluadorRasgo();
						er.setRasgoClase(r);

						rasgos.add(er);
						System.out.println(r.getRasgo()+ " - minimo: " + er.getMinimo() + ", maximo:" + er.getMaximo() + " [minimo: " + er.getRasgoClase().getMinimo() + ", maximo: " + er.getRasgoClase().getMaximo()+ ", media: " + er.getRasgoClase().getMedia()+ ", Desvio Estandar: " + er.getRasgoClase().getDesvioEstandar() + "]");
					}
				}
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} 	
		}
		EvaluadorClase ec = new EvaluadorClase(c,rasgos,claseManager);
		return ec;
		
	}

	public List<Clase> getClases() {
		if (clases == null) {
			clases = new ArrayList<Clase>();
			Set<EvaluadorClase> evaluadores = getClasificacion().keySet();
			for (EvaluadorClase c : evaluadores) {
				clases.add(c.getClase());
			}
		}
		return clases;
	}
	
	public Clase getClase(String nombreClase) {
		return claseManager.getClase(nombreClase);
	}
	
	public int countObject(){
		int count = 0;
		Set<EvaluadorClase> clases = getClasificacion().keySet();
		for(EvaluadorClase c: clases){
			List<ObjetoClasificador> objetosClase = getClasificacion().get(c);
			count = count + objetosClase.size();
		}	
		return count;
	}
	
	public double getArea(){
		double area = 0;
		DecimalFormat formater = new DecimalFormat("0.00");
		Set<EvaluadorClase> clases = getClasificacion().keySet();
		for(EvaluadorClase c: clases){
			if (!c.getClase().isObjetoReferencia()) {
				List<ObjetoClasificador> objetosClase = getClasificacion().get(
						c);
				for (ObjetoClasificador objeto : objetosClase) {
					area = area + objeto.getArea();
//					System.out
//							.println("nombre " + objeto.getName() + " - area "
//									+ formater.format(objeto.getArea())
//									+ " - alto "
//									+ formater.format(objeto.getWidth())
//									+ " - ancho "
//									+ formater.format(objeto.getHeight()));
				}
			}
		}	
		return area;
	}	
	
	/**
	 * Retorna los objetos asignados a una clase dada
	 * @param clase
	 * @return
	 */
	public List<ObjetoClasificador> getObjetosClase(Clase clase){
		for(EvaluadorClase ec: getClasificacion().keySet()){
			if (ec.getClase().equals(clase))
				return getClasificacion().get(ec);
		}
		return null;
	}

	public Integer getCantidadObjetos() {
		return cantidadObjetos;
	}

	public void setCantidadObjetos(Integer cantidadObjetos) {
		this.cantidadObjetos = cantidadObjetos;
	}
	
	public void aumentarCantidadObjetos(){
		this.cantidadObjetos++;
	}
	
	/**
	 * Metodo que compara la clase actual del objeto con la clase que se asigno inicialmente
	 * y actualiza el error de clasificacion en caso de ser necesario.
	 * @param objeto
	 */
	public void modificarClasificacion(ObjetoClasificador objeto){
		int indexOriginal = getClasificacionInicial().indexOf(objeto);
		if (indexOriginal != -1) {
			ObjetoClasificador original = getClasificacionInicial().get(indexOriginal);
			ClaseObjeto claseOriginal = original.getClases().get(0);
			ClaseObjeto claseActual = objeto.getClases().get(0);
			if (claseActual.getClase().equals(claseOriginal.getClase())) {
				if (getClasificadosIncorrectamente().contains(objeto)) {
					getClasificadosIncorrectamente().remove(objeto);
				}
			} else {
				if (!getClasificadosIncorrectamente().contains(objeto)) {
					getClasificadosIncorrectamente().add(objeto);
				}
			}
		}
	}

	public List<RubroCalidad> getRubrosCalidad() {
		return rubrosCalidad;
	}
	
	public RubroCalidad getRubroCalidad(String nombreRubro) {
		for(RubroCalidad r :rubrosCalidad){
			if(nombreRubro.equals(r.getNombre())){
				return r;
			}
		}
		return null;
	}

	public List<Grado> getGradosCalidad() {
		return gradosCalidad;
	}

	public ClaseManager getClaseManager() {
		return claseManager;
	}
	
	public RasgoManager getRasgoManager() {
		return rasgoManager;
	}

	public void setRasgoManager(RasgoManager rasgoManager) {
		this.rasgoManager = rasgoManager;
	}

	public void setClaseManager(ClaseManager claseManager) {
		this.claseManager = claseManager;
	}
	
	/**
	 * Retorna el objeto al que pertenece un pixel
	 * 
	 * @param pixel
	 * @param objetos
	 * @return
	 */
	private ObjetoClasificador getObjetoContenedor(Pixel pixel, List<ObjetoClasificador> objetos) {
		for (ObjetoClasificador obj : objetos)
			if (obj.isPertenece(pixel))
				return obj;
		return null;
	}

	/**
	 * Retorna si un pixel pertenece a algunos de los objetos
	 * 
	 * @param pixel
	 * @param objetos
	 * @return
	 */
	private boolean perteneceAAlgunObjeto(Pixel pixel, List<ObjetoClasificador> objetos) {
		for (ObjetoClasificador obj : objetos)
			if (obj.isPertenece(pixel))
				return true;
		return false;
	}
	
	/**
	 * Retorna el objeto al que pertenece el pixel
	 * 
	 * @param x
	 *            Pixel en x
	 * @param y
	 *            Pixel en y
	 * @return
	 */
	public ObjetoClasificador getObjetoContenedor(int x, int y) {
		List<ObjetoClasificador> objetos = new ArrayList<ObjetoClasificador>();
		Set<EvaluadorClase> clases = getClasificacion().keySet();
		for (EvaluadorClase c :clases) {
			List<ObjetoClasificador> objetosClase = getClasificacion().get(c);
			objetos.addAll(objetosClase);
		}
		return getObjetoContenedor(new Pixel(x, y, null), objetos);
	}
	/**
	 * Asigna a un objeto un a nueva clase
	 * @param obj Objeto
	 * @param claseNew Nueva Clase
	 */
	public void changeObjeto(ObjetoClasificador obj, Clase claseNew){
		Set<EvaluadorClase> clases = getClasificacion().keySet();
		for(EvaluadorClase c: clases){
			if(obj.getClases().size() > 0 && c.getClase().equals(obj.getClases().get(0).getClase())){
				getClasificacion().get(c).remove(obj);
				obj.removeClase(c.getClase());
			}
			else if(c.getClase().equals(claseNew)){
				getClasificacion().get(c).add(obj);
				obj.addClase(c.getClase());
			}
		}
		modificarClasificacion(obj);
	}
	
	public void changeObjeto(ObjetoClasificador obj){
		Clase claseObj=obj.getClases().get(0).getClase();
		Clase nextClase= nextClase(claseObj);
		changeObjeto(obj,nextClase);
	}

	private Clase nextClase(Clase claseObj) {
		List<Clase> clases = getClases();
		int index = clases.lastIndexOf(claseObj);
		index++;
		if (index >= clases.size()) {
			index = 0;
		}
		return clases.get(index);
	}

	public void setGradosCalidad(List<Grado> gradosCalidad) {
		this.gradosCalidad = gradosCalidad;
	}

	public Background getBackground() {
		return background;
	}

	public void setBackground(Background background) {
		this.background = background;
	}

}

